/*
 * commons is a based project implemented
 * Copyright (C) 2024 Jasmine
 */
package com.easy.redis.utils;

import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.redisson.api.*;
import org.redisson.config.Config;

import com.easy.core.constant.StringPool;
import com.easy.core.utils.ApplicationContextHolder;

/**
 * Redis 工具 TODO 待完善
 * 
 * @author Jasmine
 * @since 1.0.0
 */
public class RedisUtils {

    public static final RedissonClient CLIENT = ApplicationContextHolder.getBean(RedissonClient.class);

    /**
     * 设置缓存
     *
     * @param key 键
     * @param value 值
     */
    public static <T> void set(final String key, final T value) {
        CLIENT.getBucket(key).set(value);
    }

    /**
     * 设置缓存
     *
     * @param key 键
     * @param value 值
     * @param duration 过期时间
     */
    public static <T> void set(final String key, final T value, final Duration duration) {
        RBatch batch = CLIENT.createBatch();
        RBucketAsync<T> bucket = batch.getBucket(key);
        bucket.setAsync(value);
        bucket.expireAsync(duration);
        batch.execute();
    }

    /**
     * 查询指定缓存
     *
     * @param key 键
     * @return 值
     */
    public static <T> T get(final String key) {
        RBucket<T> bucket = CLIENT.getBucket(key);
        return bucket.get();
    }

    /**
     * 删除缓存
     *
     * @param key 键
     * @return true：设置成功；false：设置失败
     */
    public static boolean delete(final String key) {
        return CLIENT.getBucket(key).delete();
    }

    /**
     * 设置缓存过期时间
     *
     * @param key 键
     * @param timeout 过期时间（单位：秒）
     * @return true：设置成功；false：设置失败
     */
    public static boolean expire(final String key, final long timeout) {
        return expire(key, Duration.ofSeconds(timeout));
    }

    /**
     * 设置缓存过期时间
     *
     * @param key 键
     * @param duration 过期时间
     * @return true：设置成功；false：设置失败
     */
    public static boolean expire(final String key, final Duration duration) {
        return CLIENT.getBucket(key).expire(duration);
    }

    /**
     * 查询缓存剩余过期时间
     *
     * @param key 键
     * @return 缓存剩余过期时间（单位：毫秒）
     */
    public static long getTimeToLive(final String key) {
        return CLIENT.getBucket(key).remainTimeToLive();
    }

    /**
     * 是否存在指定缓存
     *
     * @param key 键
     * @return true：存在；false：不存在
     */
    public static boolean hasKey(String key) {
        RKeys keys = CLIENT.getKeys();
        return keys.countExists(getNameMapper().map(key)) > 0;
    }

    /**
     * 查询缓存列表
     *
     * @param keyPattern 键表达式
     * @return 缓存列表
     */
    public static Collection<String> keys(final String keyPattern) {
        Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(getNameMapper().map(keyPattern));
        return stream.map(key -> getNameMapper().unmap(key)).collect(Collectors.toList());
    }

    /**
     * 限流
     *
     * @param key 键
     * @param rateType 限流类型（OVERALL：全局限流；PER_CLIENT：单机限流）
     * @param rate 速率（指定时间间隔产生的令牌数）
     * @param rateInterval 速率间隔（时间间隔，单位：秒）
     * @return true：成功；false：失败
     */
    public static boolean rateLimit(String key, RateType rateType, int rate, int rateInterval) {
        RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
        rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
        return rateLimiter.tryAcquire(1);
    }

    /**
     * 格式化键，将各子键用 : 拼接起来
     *
     * @param subKeys 子键列表
     * @return 键
     */
    public static String formatKey(String... subKeys) {
        return String.join(StringPool.COLON, subKeys);
    }

    /**
     * 根据 Redisson 配置，获取名称映射器
     *
     * @return 名称映射器
     */
    private static NameMapper getNameMapper() {
        Config config = CLIENT.getConfig();
        if (config.isClusterConfig()) {
            return config.useClusterServers().getNameMapper();
        }
        if (config.isSentinelConfig()) {
            return config.useSentinelServers().getNameMapper();
        }
        return config.useSingleServer().getNameMapper();
    }

    public static boolean fairLock(String lockKey, int leaseTime, TimeUnit unit) {
        RLock fairLock = CLIENT.getFairLock(lockKey);
        try {
            boolean exist = hasKey(lockKey);
            // 已经存在，直接返回
            if (exist) {
                return false;
            }
            return fairLock.tryLock(3, leaseTime, unit);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }
}
